$$.require($$.sql.writer.Writer);
$$.require($$.sql.reader.Reader);
$$.require($$.sql.reader.Pagination);

/**
 * @class Edk.sql.DAO
 * @mix $$.sql.writer.Writer
 * @mix $$.sql.reader.Reader
 * @mix $$.sql.reader.Pagination
 * DAO = Data Access Object 数据访问对象，既可读又可写的。
 */
$$.sql.DAO = (function(){

	var connectObj;	// 数据库连接对象，只设置一次，无须重复创建。多次创建则浪费。
		
	function connectDB(cfg){
		var 
		 dbType			// 链接数据库的类型，是Access呢？MySQL呢？还是SQLServer？
		,dbPath 		// 数据库文件的路径，如果通过磁盘访问的话。
		,connectStr		// 链接数据库的字符串。
						// 数据库对象，同前面的 var connectObj; 的对象
		,connectObj = new ActiveXObject("ADODB.CONNECTION"); 
		
		if(cfg){
			dbType = cfg.dbType;
		}else if(!cfg && $$.cfg){
			dbType = $$.cfg.edk_dbType.toLowerCase();
		}else{
			dbType = 'access';	// 默认为MS Access数据库
		}
		
		switch(dbType){
			case 'access' :
				if(cfg && cfg.dbPath){
					dbPath = cfg.dbPath;
				}else if(!cfg && $$.cfg){
				    dbPath = $$.cfg.edk_isDebugging 
			    		   ? $$.cfg.edk_dbFilePath_Test
			    		   : Server.mappath($$.cfg.edk_dbFilePath);
				}
		        connectStr = "DBQ={0};DefaultDir=;DRIVER={Microsoft Access Driver (*.mdb)};".format(dbPath);
			break;
			case 'sqlservver' :
				throw '尚未实现';
			case 'mysql' :
				// 使用driver模式不用有空格在连接字符串中
				connectStr = "DRIVER={mysql odbc 5.1 driver};SERVER={0};PORT={1};UID={2};PASSWORD={3};DATABASE={4};OPTION=3";
				connectStr = connectStr.format('localhost', 3306, 'root' ,'123', 'test');            		
			break;
			case 'sqlite' :
				dbPath = 'd:\\test.db';
				connectStr = "DRIVER={SQLite3 ODBC Driver};Database=" + dbPath;
			break;
			default:
				throw '非法数据库连接类型！';
		};

		connectObj.open(connectStr);
		
		return connectObj;
	}

	/**
     * 首先通过调用的父类方法检测SQL语句是否安全的。
     * 其次，该方法执行以下的步骤：
     * 一、连接数据库；二、执行 SQL 操作。三、返回数据集合。
     * 其中连接了的数据库应该对象不应立刻销毁，应予保留，
     * 使得在当前线程的上下文（整个Edk Context）内，对象connectObj可供以后程序多次反复地使用。
     * 注意谨记：在程序最后阶段关闭数据库连接，即 $$.sql.close();
     * @private
	 * @param 	{String} 			sql 要执行的 SQL 语句（可选的）。
	 * @param   {Objcec}			cfg 配置对象，可以包含连接数据库的字符串（可选的）
	 * @return 	{ADODB.Recordset} 		数据库记录集合。
	 */
	function execute(sql, cfg, isRS){
		if(!connectObj){
			connectObj = execute.connectObj = connectDB(cfg); // call only once.
		}
		
		if(sql){
			if(isRS){
				return 			connectObj.execute(sql);
			}else{
				return row2json(connectObj.execute(sql));
			}
		}else{
			return connectObj
		}
	}
	
	/**
	 * 转换值的 ADO 类型为 JavaScript 类型。
	 * 更多常量信息请查阅文件： adovbs.inc ，该文件在系统中默认的路径为:
	 * C:\Program Files\Common Files\System\ado\adovbs.inc
	 * 其他系统请自行搜索。
	 * @private
	 * @param 	{Number} 	adoField
	 * @param 	{Any} 		value
	 * @return 	{Any}
	 */
	function sql2json(value, dataType){
		switch(dataType){
			case "string":
			case 129:		// 'char':
			case 130: 		// 'nchar':
			case 200:		// 'varchar':
			case 201:		// 'text':
			case 202:		// 'nvarchar':
			case 203:		// 'ntext':
				return String(value);
			case "number":
			case 20: 		// 'bigint':
			case 2:			// 'smallint':
			case 16: 		// 'tinyint':
			case 14: 		// 'decimal':
			case 5:			// 'double':
			case 3:
				return Number(value);
			case 11: 		// "boolean":
			case 'bol':
				return Boolean(value);
			case "date":
			case "object":
			case 135:		// 'datetime':
			case 64:		// 'fileTime':
			case 134:
			case 137:
				return new Date(value).format("yyyy-mm-dd");
			default:
				return eval(value);
		}
	}

    /**
     * 针对已查询出来的记录集，转换为 JavaScript 的 JSON 结构。
     * @param 	{RecroedSet} rs ADO 对象集合。
     * @return 	{Array}			输出的 JSON 数组。
     */
	function row2json(rs){
        var 
    	 output = [] 						// Array	输出的 JSON 数组
    	,rawArr	= rs.getRows().toArray()	// Array	getRows() 返回的数组
    	,fields = rs.Fields					// comObj	rs.Fields 字段对象，每一列都不同。
    	,fLen 	= fields.Count				// int		字段总数
		,len  	= rawArr.length / fLen		// int		记录数
		,sp									// int		位数
    	,row;								// object	构成 JSON 的行对象
		
		// 自动关闭 RecordSet 集合，可以尽快释放 RecordSet 对象占用的资源。
    	rs.Close(); 
			
		for (var i = 0; i < len; i++){
			 sp	 = i * fLen
			,row = {};
			
			for (var j = 0; j < fLen; j++){
				row[fields(j).name] = sql2json(rawArr[sp + j], fields(j).Type);
			}
			output.push(row);
		}

        return output;
    }
    
	return {
		
	constructor : function(){
		var _reader = $$.sql.reader;
		_reader.Pagination.constructor.call(this.DAO);
		_reader.Querist.constructor.call(this.DAO);
		
		// 设置表名。
		this.DAO.data.mainTable = this.getTableName();
	}
    
	,DAO : {
		 data		: null
		,execute	: execute
		,getConnObj : execute
	    
	    /**
	     * 关闭数据库连接，释放占用的资源。
	     */
	    ,close : function(){
	    	this.getConnObj().close();
	    }
	}
	
	/**
	 * 输入一个id，返回其实体。
	 * @param {Number} id
	 * @return {Object}
	 */
	,findById : function(id){
		var 
		 dao = this.DAO	// Object
		,sql			// String
		,json;			// JSON
		 
		 dao.setField(this.getTableName()  + this.getFields())
		,dao.setFilter('news.id=' + Request.QueryString('id')());
		
		sql		= dao.read();
		json	= dao.execute(sql);
		json	= json[0];
		
		return this.setValueByJson(json);
	}
	
	/**
	 * 返回分页的数据部分，另外页面部分由模板提供。
	 * @param {Number} id
	 * @return {Array}
	 */
	,findPage : function(){
		var 
		 dao = this.DAO	// Object
		,sql			// String
		,page;			// Object
		 
		dao.setField(this.getTableName() + this.getFields());
        
		sql	 = dao.read();
        page = dao.renderPage(sql);
        
		return [dao.execute(page.sql), page];
	}
	
	/**
	 * 分段查找
	 */
	,findAll : function(){
		
	}
	
	,findByLike : function(){
		
	}
	
	,findBy : function(fn){}
	};
})();

$$.sql.DAO.DAO = Object.mix($$.sql.DAO.DAO, $$.sql.writer.Writer, $$.sql.reader.Querist, $$.sql.reader.Pagination);